package com.ta.gulimall.gulimall.elasticsearch.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ta.gulimall.common.constant.ElasticsearchConstant;
import com.ta.gulimall.common.view.to.UpSpuInfoTo;
import com.ta.gulimall.gulimall.elasticsearch.config.ElasticsearchConfig;
import com.ta.gulimall.gulimall.elasticsearch.service.SearchTaService;
import com.ta.gulimall.gulimall.elasticsearch.vo.SearchDto;
import com.ta.gulimall.gulimall.elasticsearch.vo.SearchVo;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class SearchTaServiceImpl implements SearchTaService {
    @Autowired
    RestHighLevelClient restHighLevelClient;
    @Override
    public SearchVo search(SearchDto searchParam) {

        SearchRequest searchRequest = select(searchParam);
        SearchVo searchVo;
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);
            searchVo = assemble(searchResponse, searchParam);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return searchVo;
    }

    //封装java对象
    private SearchVo assemble(SearchResponse searchParam, SearchDto param) {
        SearchVo searchVo = new SearchVo();
        SearchHits hits = searchParam.getHits();

        List<UpSpuInfoTo> upSpuInfoTos = Arrays.stream(hits.getHits()).map(m -> JSONUtil.toBean(m.getSourceAsString(), UpSpuInfoTo.class))
                .collect(Collectors.toList());

        Terms aggCatalog = (Terms) searchParam.getAggregations().get("agg_catalog");
        List<SearchVo.CatalogVo> catalogVos = aggCatalog.getBuckets().stream().map(m -> new SearchVo.CatalogVo()
                        .setCatalogId(Long.valueOf(m.getKeyAsString()))
                        .setCatalogName(((Terms) m.getAggregations().get("catalogName")).getBuckets().get(0).getKeyAsString()))
                .collect(Collectors.toList());

        Terms aggBrand = (Terms) searchParam.getAggregations().get("agg_brand");
        List<SearchVo.BrandVo> brandVos = aggBrand.getBuckets().stream().map(m -> new SearchVo.BrandVo()
                        .setBrandId(Long.parseLong(m.getKeyAsString()))
                        .setBrandName(((Terms) m.getAggregations().get("agg_brandName")).getBuckets().get(0).getKeyAsString())
                        .setBrandImg(((Terms) m.getAggregations().get("agg_brandImg")).getBuckets().get(0).getKeyAsString()))
                .collect(Collectors.toList());

        Nested nestedAttr = (Nested) searchParam.getAggregations().get("nested_attr");
        Terms aggAttr = (Terms) nestedAttr.getAggregations().get("agg_attr");
        List<SearchVo.AttrVo> attrVos = aggAttr.getBuckets().stream().map(m -> new SearchVo.AttrVo()
                        .setAttrId(Long.parseLong(m.getKeyAsString()))
                        .setAttrName(((Terms) m.getAggregations().get("agg_name")).getBuckets().get(0).getKeyAsString())
                        .setAttrValue(((Terms) m.getAggregations().get("agg_value")).getBuckets().stream()
                                .map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList())))
                .collect(Collectors.toList());

        Integer total = Math.toIntExact(hits.getTotalHits().value);
        searchVo.setProducts(upSpuInfoTos)
                .setCatalogs(catalogVos)
                .setBrands(brandVos)
                .setAttrs(attrVos);

        return searchVo;
    }

    //构建dsl语句
    private SearchRequest select(SearchDto searchParam) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(boolQueryBuilder);

        if (StrUtil.isNotBlank(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
        }
        if (ObjectUtil.isNotNull(searchParam.getCatalog3Id())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
        }
        if (ObjectUtil.isNotNull(searchParam.getHasStock())) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
        }
        if (CollUtil.isNotEmpty(searchParam.getBrandIds())) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", searchParam.getBrandIds()));
        }
        String skuPrice = searchParam.getSkuPrice();
        if (ObjectUtil.isNotNull(skuPrice)) {
            Object from = null;
            Object to = null;
            String[] s = searchParam.getSkuPrice().split("_");
            if(skuPrice.startsWith("_")){
                to = s[1];
            } else if (skuPrice.endsWith("_")) {
                from = s[0];
            } else {
                from = s[0];
                to = s[1];
            }
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(from).lte(to));
        }
        if (CollUtil.isNotEmpty(searchParam.getAttrs())) {
            searchParam.getAttrs().forEach(f -> {
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",
                        QueryBuilders.boolQuery()
                                .must(QueryBuilders.termQuery("attrs.attrId", f.split("_")[0]))
                                .must(QueryBuilders.termsQuery("attrs.attrValue", f.split("_")[1].split(":"))),
                        ScoreMode.None));
            });
        }
        if (ObjectUtil.isNotNull(searchParam.getSort())) {
            String[] s = searchParam.getSort().split("_");
            searchSourceBuilder.sort(s[0], s[1].equalsIgnoreCase("desc") ? SortOrder.DESC : SortOrder.ASC);
        }

        //聚合分析
        searchSourceBuilder.aggregation(AggregationBuilders
                .terms("agg_catalog").field("catalogId").subAggregation(AggregationBuilders
                        .terms("catalogName").field("catalogName")));
        searchSourceBuilder.aggregation(AggregationBuilders
                .terms("agg_brand").field("brandId").subAggregation(AggregationBuilders
                        .terms("agg_brandName").field("brandName")).subAggregation(AggregationBuilders
                        .terms("agg_brandImg").field("brandImg")));
        searchSourceBuilder.aggregation(AggregationBuilders.nested("nested_attr", "attrs").subAggregation(AggregationBuilders
                .terms("agg_attr").field("attrs.attrId").subAggregation(AggregationBuilders
                        .terms("agg_name").field("attrs.attrName")).subAggregation(AggregationBuilders
                        .terms("agg_value").field("attrs.attrValue"))));

        SearchRequest searchRequest = new SearchRequest(ElasticsearchConstant.PRODUCT_INDEX)
                .source(searchSourceBuilder);

        return searchRequest;
    }
}
